package com.csmd.springframe;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.security.spec.RSAOtherPrimeInfo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author gary <498982703@qq.com>
 * @Version V1.0.0
 * @Since 1.0
 * @Date 2020/10/22
 */
public class CsmdApplicationContext {

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
    private Map<String, Object> singletonObjects = new HashMap<>();

    public CsmdApplicationContext(Class configClass)  {
        scan(configClass);
        //实例化非懒加载单例bean
        //1 实例化
        //2 属性填充
        //3 Aware回调
        //4 初始化
        //5 添加到单例池
        instanceSingletonBean();
    }

    private void instanceSingletonBean() {
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if (beanDefinition.getScope().equals(ScopeEnum.singleton)) {
                Object bean = doCreateBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }

        }

    }
    //基于BeanDefinition来创建Bean
    private Object doCreateBean(String beanName, BeanDefinition beanDefinition) {
        Class beanClass = beanDefinition.getBeanClass();

        try {
            //实例化
            Constructor declaredConstructor = beanClass.getDeclaredConstructor();
            Object instance = declaredConstructor.newInstance();
            //填充属性
            Field[] fields = beanClass.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    String name = field.getName();
                    Object bean = getBean(name);
                    field.setAccessible(true);
                    field.set(instance, bean);
                }
            }

            //Aware回调
            if (instance instanceof BeanNameWare) {
                BeanNameWare beanNameWare = (BeanNameWare) instance;
                beanNameWare.setBeanName(beanName);
            }
            //初始化
            if (instance instanceof InitializingBean) {
                InitializingBean initializingBean = (InitializingBean) instance;
                initializingBean.afterPropertiesSet();
            }
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.postProcessAfterInitialization(beanName, instance);
            }
            return instance;


        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        return null;


    }

    private void scan(Class configClass) {
        //扫描class ,转化为BeanDefintion对象，最后添加到beanDefinitionMap中
        //得到扫描包路径
        ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        String packagePath = componentScan.value();
        System.out.println(packagePath);
        //扫描包路得到classList
        List<Class> classes = genBeanClasses(packagePath);
        //遍历classes 得到BeanDefinition
        for (Class clazz : classes) {
            if (clazz.isAnnotationPresent(Component.class)) {
                BeanDefinition beanDefinition = new BeanDefinition();
                beanDefinition.setBeanClass(clazz);
                Component component = (Component) clazz.getAnnotation(Component.class);
                String beanName = component.value();

                if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                    try {
                        BeanPostProcessor instance = (BeanPostProcessor) clazz.getDeclaredConstructor().newInstance();
                        beanPostProcessorList.add(instance);
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    }
                }
                //解析scope
                if (clazz.isAnnotationPresent(Scope.class)) {
                    Scope scope = (Scope) clazz.getAnnotation(Scope.class);
                    String scopeValue = scope.value();
                    if (ScopeEnum.singleton.name().equals(scopeValue)) {
                        beanDefinition.setScope(ScopeEnum.singleton);
                    }else{
                        beanDefinition.setScope(ScopeEnum.prototype);
                    }

                }else {
                    beanDefinition.setScope(ScopeEnum.singleton);

                }

                beanDefinitionMap.put(beanName, beanDefinition);

            }

        }
    }

    private List<Class> genBeanClasses(String packagePath) {
        List<Class> beanClass = new ArrayList<>();
        ClassLoader classLoader = CsmdApplicationContext.class.getClassLoader();
        packagePath = packagePath.replace(".", "/");

        URL resource = classLoader.getResource(packagePath);
        assert resource != null;
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                String fileName = f.getAbsolutePath();
                System.out.println(fileName);
                if (fileName.endsWith(".class")) {
                    String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                    className = className.replace("\\", ".");
                    try {
                        Class<?> classz = classLoader.loadClass(className);
                        beanClass.add(classz);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }


                }

            }
        }
        return beanClass;
    }

    public Object getBean(String beanName) {
        if (singletonObjects.containsKey(beanName)) {
            return singletonObjects.get(beanName);
        }else {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            return doCreateBean(beanName, beanDefinition);

        }
    }




}
